/**
 * KopsoxFramework
 * Copyright (C) 2010 Free Software Foundation, Inc. <http://fsf.org/>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kopsox.data.container.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.kopsox.comm.listener.Observer;
import org.kopsox.comm.message.ErrorMessage;
import org.kopsox.comm.message.ValueMessage;
import org.kopsox.comm.message.impl.ErrorMessageImpl;
import org.kopsox.comm.message.impl.ValueMessageImpl;
import org.kopsox.data.ContainerDescriptor;
import org.kopsox.data.ValidateableValue;
import org.kopsox.data.container.ValueContainer;
import org.kopsox.general.priority.PriorityEnum;
import org.kopsox.general.type.ErrorTypeEnum;
import org.kopsox.general.type.ValueType;
import org.kopsox.general.type.ValueTypeEnum;
import org.kopsox.util.comm.ObserverTreeSet;
import org.kopsox.util.persistence.PersistenceDelegate;
import org.kopsox.util.persistence.impl.KopsoxPersistenceException;
import org.kopsox.util.validation.ValidatorSet;
import org.kopsox.validation.ContainerValidator;
import org.kopsox.validation.Validateable;
import org.kopsox.validation.Validator;

/**
 * @author Konrad Renner - 24.06.2010
 *
 */
public class ValueContainerImpl implements ValueContainer {
	
	
	private static final long serialVersionUID = 6057768298699098367L;
	protected final Map<String,  ValidateableValue<Serializable>> valueContainer;
	
	//Set of names from the 'dirty'-Values
	private final Set<String> dirtyValues;
	
	//Observers
	private final ObserverTreeSet observers;
	
	//Validators
	protected final ValidatorSet validators;
	
	//PersistenceDelegate
	private PersistenceDelegate persistDelegate;
	
	//Descriptor of the container
	protected ContainerDescriptor descriptor;
	
	public ValueContainerImpl(){
		this(new ContainerDescriptorImpl.ContainerDescriptorBuilder().build());
	}
	
	public ValueContainerImpl(ContainerDescriptor descriptor){
		this.valueContainer = new ConcurrentHashMap<String, ValidateableValue<Serializable>>();
		this.descriptor = descriptor==null?new ContainerDescriptorImpl.ContainerDescriptorBuilder().build():descriptor;
		this.observers = new ObserverTreeSet();
		this.dirtyValues = new HashSet<String>();
		this.validators = new ValidatorSet();
		this.validators.setAutomaticDelegationToHandlers(this.descriptor.isAutomaticErrorDelegationEnabled());
	}

	
	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValueContainer#getDescriptor()
	 */
	@Override
	public ContainerDescriptor getDescriptor() {
		return this.descriptor;
	}
	
	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValueContainer#setDescriptor(org.kopsox.data.ContainerDescriptor)
	 */
	@Override
	public void setDescriptor(ContainerDescriptor desc) {
		this.descriptor = desc;
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValueContainer#getValue(java.lang.String)
	 */
	@Override
	public ValidateableValue<Serializable> getValue(final String valueName) {
		return this.valueContainer.get(valueName);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<ErrorMessage> setValue(final String valueName,ValidateableValue value) {
		//Null is not allowed
		if(valueName == null || value == null) {
			List<ErrorMessage> error = new ArrayList<ErrorMessage>();
			error.add(new ErrorMessageImpl(PriorityEnum.HIGHER.getPriority(), ErrorTypeEnum.NULLPARAMETER));
			return error;
		}
		
		ValueMessageImpl message;
		
		if(this.valueContainer.containsKey(valueName)) {
			ValidateableValue<Serializable> oldValue = this.valueContainer.get(valueName);
			oldValue.getValueDescriptor().setAutoValidationEnabled(this.descriptor==null?true:this.descriptor.isAutoValidationEnabled());
			
			ValidateableValue<Serializable> newValue = oldValue.changeValue(value.getValue());
			
			message = new ValueMessageImpl(newValue);
			message.setOldValue(oldValue.getValue());
			message.setTypeFromOldValue(oldValue.getType());
			message.setMessageSender(this);
			
			if(newValue.getValidationErrors().size() > 0){
				return newValue.getValidationErrors();
			}
			
			if(!this.dirtyValues.contains(valueName)){
				this.dirtyValues.add(valueName);
			}
			
			this.valueContainer.put(valueName, newValue);
		}else {
			message = new ValueMessageImpl(value);
			message.setMessageSender(this);
			
			this.valueContainer.put(valueName, value);
		}
		
		this.notifyObservers(message);
		
		return new ArrayList<ErrorMessage>();
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValidateableContainer#getValuesForValidation()
	 */
	@Override
	public Collection<? extends Validateable> getValuesForValidation() {
		return this.valueContainer.values();
	}

	
	/* (non-Javadoc)
	 * @see org.kopsox.comm.listener.Observed#addObserver(org.kopsox.comm.listener.Observer)
	 */
	@Override
	public synchronized boolean addObserver(final Observer observer) {
		return this.observers.add(observer);
	}

	/* (non-Javadoc)
	 * @see org.kopsox.comm.listener.Observed#removeObserver(org.kopsox.comm.listener.Observer)
	 */
	@Override
	public synchronized boolean removeObserver(final Observer observer) {
		return this.observers.remove(observer);
	}
	
	@Override
	public Collection<Observer> getAllObservers() {
		return this.observers;
	}

	@Override
	public synchronized boolean addAllObservers(Collection<Observer> observer) {
		return this.observers.addAll(observer);
	}
	
	@Override
	public void notifyObservers(ValueMessage message){
		//Notify the Observers for each dirty value
		this.observers.notification(message);
	}
	
	/* (non-Javadoc)
	 * @see org.kopsox.util.persistence.Persistable#load()
	 */
	@Override
	public List<ErrorMessage> load()throws KopsoxPersistenceException {
		if(this.persistDelegate == null) {
			List<ErrorMessage> error = new ArrayList<ErrorMessage>();
			error.add(new ErrorMessageImpl(PriorityEnum.MEDIUM.getPriority(), ErrorTypeEnum.NOPERSISTENCEDELEGATE));
			return error;
		}
		
		return this.persistDelegate.loadInto(this);
	}

	/* (non-Javadoc)
	 * @see org.kopsox.util.persistence.Persistable#persist()
	 */
	@Override
	public List<ErrorMessage> persist()throws KopsoxPersistenceException {
		if(this.persistDelegate == null) {
			List<ErrorMessage> error = new ArrayList<ErrorMessage>();
			error.add(new ErrorMessageImpl(PriorityEnum.MEDIUM.getPriority(), ErrorTypeEnum.NOPERSISTENCEDELEGATE));
			return error;
		}
		return  this.persistDelegate.persistFrom(this);
	}

	/* (non-Javadoc)
	 * @see org.kopsox.util.persistence.Persistable#registerPersistenceDelegate(org.kopsox.util.persistence.PersistenceDelegate)
	 */
	@Override
	public boolean registerPersistenceDelegate(PersistenceDelegate delegate) {
		this.persistDelegate = delegate;
		return true;
	}

	/* (non-Javadoc)
	 * @see org.kopsox.util.persistence.Persistable#unregisterPersistenceDelegate()
	 */
	@Override
	public boolean unregisterPersistenceDelegate() {
		this.persistDelegate = null;
		return true;
	}

	@Override
	public ValueType getType() {
		return ValueTypeEnum.CONTAINER;
	}

	@Override
	public Set<String> getNamesOfDirtyValues() {
		return this.dirtyValues;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((valueContainer == null) ? 0 : valueContainer.hashCode());
		result = prime * result + (this.descriptor==null?0:this.descriptor.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ValueContainerImpl other = (ValueContainerImpl) obj;
		if (valueContainer == null) {
			if (other.valueContainer != null)
				return false;
		} else if (!valueContainer.equals(other.valueContainer))
			return false;
		if (getDescriptor() == null) {
            if (other.getDescriptor() != null)
                    return false;
		} else if (!getDescriptor().equals(other.getDescriptor()))
            return false;
		
		return true;
	}

	@Override
	public String toString(){
		StringBuilder sb = new StringBuilder();
		sb.append("ValueContainerImpl:{");
		sb.append("ContainerDescriptor=");
		sb.append(this.descriptor);
		sb.append(",valueContainer=");
		sb.append(this.valueContainer);
		sb.append(",dirtyValues:");
		sb.append(this.dirtyValues);
		sb.append(",validators:");
		sb.append(this.validators);
		sb.append(",observers:");
		sb.append(this.observers);
		sb.append('}');
		
		return sb.toString();
	}

	@Override
	public int getSize() {
		return this.valueContainer.size();
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValueContainer#containsValue(java.lang.String)
	 */
	@Override
	public boolean containsValue(String valueName) {
		return this.valueContainer.containsKey(valueName);
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValueContainer#isEmpty()
	 */
	@Override
	public boolean isEmpty() {
		return this.valueContainer.isEmpty();
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValueContainer#getValues()
	 */
	@Override
	public Collection<ValidateableValue<Serializable>> getValues() {
		return this.valueContainer.values();
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValidateableContainer#addAllContainerValidators(java.util.Collection)
	 */
	@Override
	public void addAllContainerValidators(
			Collection<ContainerValidator> validator) {
		this.validators.addAll(validator);
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValidateableContainer#addContainerValidator(org.kopsox.validation.ContainerValidator)
	 */
	@Override
	public void addContainerValidator(ContainerValidator validator) {
		this.validators.add(validator);
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValidateableContainer#getValidationErrors()
	 */
	@Override
	public List<ErrorMessage> getValidationErrors() {
		return this.validators.getValidationErrors();
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValidateableContainer#removeContainerValidator(org.kopsox.validation.ContainerValidator)
	 */
	@Override
	public void removeContainerValidator(ContainerValidator validator) {
		this.validators.remove(validator);
	}
	
	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValidateableContainer#isContainerValidatorRegistered(org.kopsox.validation.ContainerValidator)
	 */
	@Override
	public boolean isContainerValidatorRegistered(ContainerValidator validator) {
		return this.validators.contains(validator);
	}

	/* (non-Javadoc)
	 * @see org.kopsox.data.container.ValidateableContainer#validateContainer()
	 */
	@Override
	public boolean validateContainer() {
		boolean validationOk = true;
		
		//Clear the messages
		this.validators.getValidationErrors().clear();
		
		Iterator<Validator> iter = this.validators.iterator();
		
		while(iter.hasNext()) {
			ContainerValidator val = (ContainerValidator)iter.next();
			
			boolean ok = val.validateValueContainer(this);
			
			if(!ok) {
				this.validators.getValidationErrors().addAll(val.getValidationErrors());
				
				validationOk = false;
				
				if(this.validators.isAutomaticDelegationToHandlersEnabled()) {
					val.delegateErrorsToHandlers();
				}
				
				//if the Validator is an 'Show-Stopping'-Validtor, stop the process
				if(val.isShowStoppingValidator()) {
					break;
				}
					
			}
		}
		
		return validationOk;
	}

	@Override
	public Collection<String> getValuesAsStrings() {
		Collection<String> ret = new ArrayList<String>();
		
		for(ValidateableValue<Serializable> value:this.valueContainer.values()){
			ret.add(value.getValue().toString());
		}
		
		return ret;
	}
}
